#include "LeGraphicsItem.h"

#include "LeGraphicsHandleItem.h"
#include "LeGraphicsItemLayer.h"
#include "LeGraphicsScene.h"

#include <QDebug>
#include <QPainter>
#include <QPainterPathStroker>
#include <QStyleOptionGraphicsItem>


/*
 * Quick stats about testcase-2.xml, testcase-5.xml and testcase-6.xml:
 *
 * Their UserDictionary and StandardDictionary contains on average:
 * - 20% Circle      (Min 16%, Max 22%)
 * - 18% Contour     (Min  3%, Max 36%)
 * -  1% Oval        (Min  0%, Max  4%)
 * - 55% RectCenter  (Min 28%, Max 81%)
 * -  4% PolyLine    (Min  0%, Max  9%)
 * -  3% UserSpecial (Min  0%, Max  6%)
 *
 * Their Step contains on average:
 * - 34% Circle      (Min 26%, Max 81%)
 * -  5% Contour     (Min  0%, Max 10%)
 * -  3% RectCenter  (Min  0%, Max 21%)
 * -  5% Line        (Min  0%, Max  5%)
 * - 50% PolyLine    (Min  9%, Max 58%)
 * -  4% UserSpecial (Min  0%, Max  5%)
 */


/********************************************************************
 *
 * GraphicsItem
 *
 *******************************************************************/

LeGraphicsItem::LeGraphicsItem(LeGraphicsFeature role, QGraphicsObject *parent):
    QGraphicsObject(parent),
    m_featureType(role)
{
    // FIXME: Should be done at higher level
    setFlag(ItemIsSelectable);
    setAcceptHoverEvents(true);
}

LeGraphicsItem::~LeGraphicsItem()
{
}

void LeGraphicsItem::setFeatureType(LeGraphicsFeature role)
{
    m_featureType = role;
}

LeGraphicsFeature LeGraphicsItem::featureType() const
{
    return m_featureType;
}

void LeGraphicsItem::setFeatureMetaData(const QMap<QString, QString> &metaData)
{
    m_featureMetaData = metaData;
}

QMap<QString, QString> LeGraphicsItem::featureMetaData() const
{
    return m_featureMetaData;
}

void LeGraphicsItem::setStateFlag(LeGraphicsStyle::StateFlag which, bool on)
{
    if (m_state.testFlag(which) == on)
        return;
    m_state.setFlag(which, on);
    update();
}

bool LeGraphicsItem::stateFlag(LeGraphicsStyle::StateFlag which) const
{
    return m_state.testFlag(which);
}

void LeGraphicsItem::setState(LeGraphicsStyle::State states)
{
    if (m_state == states)
        return;
    m_state = states;
    update();
}

LeGraphicsStyle::State LeGraphicsItem::state() const
{
    if (isSelected())
        return m_state | LeGraphicsStyle::Selected;
    return m_state;
}

LeGraphicsItemLayer *LeGraphicsItem::layer() const
{
    QGraphicsObject *parent;
    for (parent = parentObject(); parent != nullptr; parent = parent->parentObject())
        if (parent->type() == LeGraphicsItemLayer::Type)
            return static_cast<LeGraphicsItemLayer*>(parent);

    Q_ASSERT_X(parent != nullptr, "GraphicsItem::layer()", "GraphicsItem has no GraphicsLayer ancestor");
    Q_UNREACHABLE();
}

LeGraphicsScene *LeGraphicsItem::graphicsScene() const
{
    return qobject_cast<LeGraphicsScene*>(scene());
}

QList<LeGraphicsHandleItem *> LeGraphicsItem::handles() const
{
    return m_handleMap.values();
}

LeGraphicsHandleItem *LeGraphicsItem::addHandle(LeGraphicsHandleRole role)
{
    auto handle = new LeGraphicsHandleItem(role);
    handle->setParentItem(this);
    handle->hide();
    handle->setFlag(ItemSendsGeometryChanges);
    handle->installItemChangeFilter(this);
    m_handleMap.insert(role, handle);
    return handle;
}

LeGraphicsHandleItem *LeGraphicsItem::handle(LeGraphicsHandleRole role)
{
    return m_handleMap.value(role, nullptr);
}

QPointF LeGraphicsItem::handlePositionChangeFilter(LeGraphicsHandleRole role, const QPointF &value)
{
    Q_UNUSED(role)
    return value;
}

void LeGraphicsItem::installItemChangeFilter(LeGraphicsItem *filter)
{
    m_itemChangeFilterItems.append(filter);
}

void LeGraphicsItem::removeItemChangeFilter(LeGraphicsItem *filter)
{
    m_itemChangeFilterItems.removeOne(filter);
}

QVariant LeGraphicsItem::itemChangeFilter(LeGraphicsItem *item,
                                          QGraphicsItem::GraphicsItemChange change,
                                          const QVariant &value)
{
    auto *handle = qgraphicsitem_cast<LeGraphicsHandleItem*>(item);
    if (m_handleMap.values().contains(handle) && change == ItemPositionChange)
        return handlePositionChangeFilter(handle->handleRole(), value.toPointF());
    return value;
}

QVariant LeGraphicsItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
    if (change == ItemSelectedHasChanged && !isSelected())
        for (auto handle: m_handleMap)
            handle->hide();

    if (change != ItemPositionChange)
        return value;

    QVariant newValue = value;
    for (LeGraphicsItem *filter: m_itemChangeFilterItems)
        newValue = filter->itemChangeFilter(this, change, newValue);

    return newValue;
}

template<class T>
T* LeGraphicsItem::createClone() const
{
    T *item = new T(m_featureType);
    item->m_state = 0; // Is it OK to clear all flags?
    item->setPos(pos());
    item->setTransform(transform());
    return item;
}

void LeGraphicsItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
    setStateFlag(LeGraphicsStyle::Highlighted, true);
    if (graphicsScene()->toolTipEnabled())
        setToolTip(graphicsScene()->graphicsStyle()->formatFeatureMetaData(featureMetaData()));
    else
        setToolTip(QString());
    QGraphicsItem::hoverEnterEvent(event);
    update();
}

void LeGraphicsItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
    setStateFlag(LeGraphicsStyle::Highlighted, false);
    QGraphicsItem::hoverLeaveEvent(event);
    update();
}

/********************************************************************
 *
 * GraphicsCompostiteItem
 *
 *******************************************************************/

LeGraphicsCompositeItem::LeGraphicsCompositeItem(LeGraphicsFeature role, QGraphicsObject *parent):
    LeGraphicsItem(role, parent)
{
    setFlag(ItemHasNoContents);
}

LeGraphicsCompositeItem::~LeGraphicsCompositeItem()
{

}

LeGraphicsItem *LeGraphicsCompositeItem::clone() const
{
    auto clonedParentItem = createClone<LeGraphicsCompositeItem>();
    for (auto childItem: childItems())
    {
        auto item = static_cast<LeGraphicsItem*>(childItem);
        auto clonedItem = item->clone();
        clonedItem->setParentItem(clonedParentItem);
    }
    return clonedParentItem;
}

QRectF LeGraphicsCompositeItem::boundingRect() const
{
    return QRectF();
}

QPainterPath LeGraphicsCompositeItem::shape() const
{
    return QPainterPath();
}

void LeGraphicsCompositeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(painter)
    Q_UNUSED(option)
    Q_UNUSED(widget)
}

/********************************************************************
 *
 * GraphicsCircleItem
 *
 *******************************************************************/

LeGraphicsCircleItem::LeGraphicsCircleItem(LeGraphicsFeature featureType, QGraphicsObject *parent):
    LeGraphicsItem(featureType, parent)
{

}

LeGraphicsCircleItem::~LeGraphicsCircleItem()
{

}

void LeGraphicsCircleItem::setDiameter(qreal diameter)
{
    prepareGeometryChange();
    m_diameter = diameter;
    const qreal radius = m_diameter/2.0;
    m_shape = QPainterPath();
    m_shape.addEllipse(QPointF(0, 0), radius, radius);
    m_boundingRect = m_shape.boundingRect();
    update();
}

qreal LeGraphicsCircleItem::diameter() const
{
    return m_diameter;
}

LeGraphicsItem *LeGraphicsCircleItem::clone() const
{
    auto item = createClone<LeGraphicsCircleItem>();
    item->m_diameter = m_diameter;
    item->m_boundingRect = m_boundingRect;
    item->m_shape = m_shape;
    return item;
}

QRectF LeGraphicsCircleItem::boundingRect() const
{
    return m_boundingRect;
}

QPainterPath LeGraphicsCircleItem::shape() const
{
    return m_shape;
}

void LeGraphicsCircleItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(widget)

    GraphicsStyleOption itemOption;
    itemOption.exposedRect = option->exposedRect;
    itemOption.palette = layer()->graphicsPalette(featureType());
    itemOption.state = state();
    LeGraphicsStyle *style = graphicsScene()->graphicsStyle();
    style->drawCircle(&itemOption, painter, QPointF(0, 0), m_diameter);
}

/********************************************************************
 *
 * GraphicsRectItem
 *
 *******************************************************************/

LeGraphicsRectItem::LeGraphicsRectItem(LeGraphicsFeature featureType, QGraphicsObject *parent):
    LeGraphicsItem(featureType, parent)
{

}

LeGraphicsRectItem::~LeGraphicsRectItem()
{

}

void LeGraphicsRectItem::setWidth(qreal width)
{
    prepareGeometryChange();
    m_rect.setWidth(width);
    m_rect.moveCenter(QPointF(0, 0));
    m_shape = QPainterPath();
    m_shape.addRect(m_rect);
    update();
}

qreal LeGraphicsRectItem::width() const
{
    return m_rect.width();
}

void LeGraphicsRectItem::setHeight(qreal height)
{
    prepareGeometryChange();
    m_rect.setHeight(height);
    m_rect.moveCenter(QPointF(0, 0));
    m_shape = QPainterPath();
    m_shape.addRect(m_rect);
    update();
}

qreal LeGraphicsRectItem::height() const
{
    return m_rect.height();
}

void LeGraphicsRectItem::setSize(const QSizeF &size)
{
    setHeight(size.height());
    setWidth(size.width());
}

LeGraphicsItem *LeGraphicsRectItem::clone() const
{
    auto item = createClone<LeGraphicsRectItem>();
    item->m_rect = m_rect;
    item->m_shape = m_shape;
    return item;
}

QRectF LeGraphicsRectItem::boundingRect() const
{
    return m_rect;
}

QPainterPath LeGraphicsRectItem::shape() const
{
    return m_shape;
}

void LeGraphicsRectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(widget)

    GraphicsStyleOption itemOption;
    itemOption.exposedRect = option->exposedRect;
    itemOption.palette = layer()->graphicsPalette(featureType());
    itemOption.state = state();
    LeGraphicsStyle *style = graphicsScene()->graphicsStyle();
    style->drawRect(&itemOption, painter, m_rect);
}

/********************************************************************
 *
 * GraphicsFilledPathItem
 *
 *******************************************************************/

LeGraphicsFilledPathItem::LeGraphicsFilledPathItem(LeGraphicsFeature featureType, QGraphicsObject *parent):
    LeGraphicsItem(featureType, parent)
{
    setFlag(ItemUsesExtendedStyleOption);
}

LeGraphicsFilledPathItem::~LeGraphicsFilledPathItem()
{

}

void LeGraphicsFilledPathItem::setPath(const QPainterPath &path)
{
    prepareGeometryChange();
    m_path = path;
    m_boundingRect = m_path.boundingRect();
    update();
}

QPainterPath LeGraphicsFilledPathItem::path() const
{
    return m_path;
}

LeGraphicsItem *LeGraphicsFilledPathItem::clone() const
{
    auto item = createClone<LeGraphicsFilledPathItem>();
    item->m_path = m_path;
    item->m_boundingRect = m_boundingRect;
    return item;
}

QRectF LeGraphicsFilledPathItem::boundingRect() const
{
    return m_boundingRect;
}

QPainterPath LeGraphicsFilledPathItem::shape() const
{
    return m_path;
}

void LeGraphicsFilledPathItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(widget)

    GraphicsStyleOption itemOption;
    itemOption.exposedRect = option->exposedRect;
    itemOption.palette = layer()->graphicsPalette(featureType());
    itemOption.state = state();
    LeGraphicsStyle *style = graphicsScene()->graphicsStyle();
    style->fillPath(&itemOption, painter, m_path);
}

/********************************************************************
 *
 * GraphicsStrokedPathItem
 *
 *******************************************************************/

LeGraphicsStrokedPathItem::LeGraphicsStrokedPathItem(LeGraphicsFeature featureType, QGraphicsObject *parent):
    LeGraphicsItem(featureType, parent)
{
    setFlag(ItemUsesExtendedStyleOption);
}

LeGraphicsStrokedPathItem::~LeGraphicsStrokedPathItem()
{

}

qreal LeGraphicsStrokedPathItem::strokeWidth()
{
    return m_strokeWidth;
}

void LeGraphicsStrokedPathItem::setPath(const QPainterPath &path, qreal width)
{
    prepareGeometryChange();
    m_path = path;
    m_strokeWidth = width;
    QPainterPathStroker stroker;
    stroker.setCapStyle(Qt::RoundCap);
    stroker.setJoinStyle(Qt::RoundJoin);
    stroker.setDashPattern(Qt::SolidLine);
    if (qFuzzyCompare(m_strokeWidth, 0.0))
        stroker.setWidth(0.000001);
    else
        stroker.setWidth(m_strokeWidth);
    m_shape = stroker.createStroke(m_path).simplified();
    m_boundingRect = m_shape.boundingRect();
    update();
}

QPainterPath LeGraphicsStrokedPathItem::path() const
{
    return m_path;
}

LeGraphicsItem *LeGraphicsStrokedPathItem::clone() const
{
    auto item = createClone<LeGraphicsStrokedPathItem>();
    item->m_path = m_path;
    item->m_strokeWidth = m_strokeWidth;
    item->m_shape = m_shape;
    item->m_boundingRect = m_boundingRect;
    return item;
}

QRectF LeGraphicsStrokedPathItem::boundingRect() const
{
    return m_boundingRect;
}

QPainterPath LeGraphicsStrokedPathItem::shape() const
{
    return m_shape;
}

void LeGraphicsStrokedPathItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(widget)

    GraphicsStyleOption itemOption;
    itemOption.exposedRect = option->exposedRect;
    itemOption.palette = layer()->graphicsPalette(featureType());
    itemOption.state = state();
    LeGraphicsStyle *style = graphicsScene()->graphicsStyle();
    style->strokePath(&itemOption, painter, m_path, m_shape, m_strokeWidth);
}

#include <QFont>
#include <QFontMetricsF>

LeGraphicsLabelItem::LeGraphicsLabelItem(LeGraphicsFeature featureType, QGraphicsObject *parent)
    : LeGraphicsItem(featureType, parent)
{
    setFlag(QGraphicsItem::ItemIgnoresTransformations);
}

LeGraphicsLabelItem::~LeGraphicsLabelItem()
{

}

void LeGraphicsLabelItem::setFont(const QFont &font)
{
    if (m_font == font)
        return;
    prepareGeometryChange();
    m_font = font;
    updateGeometry();
}

QFont LeGraphicsLabelItem::font() const
{
    return m_font;
}

void LeGraphicsLabelItem::setText(const QString &text)
{
    if (m_text == text)
        return;

    prepareGeometryChange();
    m_text = text;
    updateGeometry();
}

QString LeGraphicsLabelItem::text() const
{
    return m_text;
}

void LeGraphicsLabelItem::updateGeometry()
{
    QFontMetricsF fontMetric(m_font);
    m_boundingRect = fontMetric.boundingRect(QRectF(), Qt::AlignCenter, m_text);
    m_shape = QPainterPath();
    m_shape.addRect(m_boundingRect);
}

LeGraphicsItem *LeGraphicsLabelItem::clone() const
{
    auto item = createClone<LeGraphicsLabelItem>();
    item->m_text = m_text;
    item->m_boundingRect = m_boundingRect;
    return item;
}

QRectF LeGraphicsLabelItem::boundingRect() const
{
    return m_boundingRect;
}

QPainterPath LeGraphicsLabelItem::shape() const
{
    return m_shape;
}

void LeGraphicsLabelItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(widget)

    GraphicsStyleOption itemOption;
    itemOption.exposedRect = option->exposedRect;
    itemOption.palette = layer()->graphicsPalette(featureType());
    itemOption.state = state();
    // TODO: Use LeGraphicsStyle
    //LeGraphicsStyle *style = graphicsScene()->graphicsStyle();
    //style->drawText(&itemOption, painter, m_text);
    const QColor color = itemOption.palette.color(LeGraphicsPalette::Feature);
    painter->setPen(color);
    painter->setFont(m_font);
    painter->drawText(m_boundingRect, Qt::AlignCenter, m_text);
}
